/*
 * Copyright 2021 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef SKSL_OPERATOR
#define SKSL_OPERATOR

#include <cstdint>
#include <string_view>

namespace SkSL {
class Context;
class Type;

enum class OperatorKind : uint8_t {
    PLUS,
    MINUS,
    STAR,
    SLASH,
    PERCENT,
    SHL,
    SHR,
    LOGICALNOT,
    LOGICALAND,
    LOGICALOR,
    LOGICALXOR,
    BITWISENOT,
    BITWISEAND,
    BITWISEOR,
    BITWISEXOR,
    EQ,
    EQEQ,
    NEQ,
    LT,
    GT,
    LTEQ,
    GTEQ,
    PLUSEQ,
    MINUSEQ,
    STAREQ,
    SLASHEQ,
    PERCENTEQ,
    SHLEQ,
    SHREQ,
    BITWISEANDEQ,
    BITWISEOREQ,
    BITWISEXOREQ,
    PLUSPLUS,
    MINUSMINUS,
    COMMA
};

enum class OperatorPrecedence : uint8_t {
    kParentheses = 1,
    kPostfix = 2,
    kPrefix = 3,
    kMultiplicative = 4,
    kAdditive = 5,
    kShift = 6,
    kRelational = 7,
    kEquality = 8,
    kBitwiseAnd = 9,
    kBitwiseXor = 10,
    kBitwiseOr = 11,
    kLogicalAnd = 12,
    kLogicalXor = 13,
    kLogicalOr = 14,
    kTernary = 15,
    kAssignment = 16,
    kSequence = 17,          // a comma-separated sequence
    kExpression = kSequence, // a top-level expression, anywhere in a statement
    kStatement = 18,         // a standalone expression-statement
};

class Operator {
public:
    using Kind = OperatorKind;

    Operator(Kind op) : fKind(op) {}

    Kind kind() const
    {
        return fKind;
    }

    bool isEquality() const
    {
        return fKind == Kind::EQEQ || fKind == Kind::NEQ;
    }

    OperatorPrecedence getBinaryPrecedence() const;

    // Returns the operator name surrounded by the expected whitespace for a tidy binary expression.
    const char *operatorName() const;

    // Returns the operator name without any surrounding whitespace.
    std::string_view tightOperatorName() const;

    // Returns true if op is `=` or any compound-assignment operator (`+=`, `-=`, etc.)
    bool isAssignment() const;

    // Returns true if op is any compound-assignment operator (`+=`, `-=`, etc.) but false for `=`
    bool isCompoundAssignment() const;

    // Given a compound-assignment operator, returns the non-assignment version of the operator
    // (e.g. `+=` becomes `+`)
    Operator removeAssignment() const;

    /* *
     * Defines the set of relational (comparison) operators:
     * <  <=  >  >=
     */
    bool isRelational() const;

    /* *
     * Defines the set of operators which are only valid on integral types:
     * <<  <<=  >>  >>=  &  &=  |  |=  ^  ^=  %  %=
     */
    bool isOnlyValidForIntegralTypes() const;

    /* *
     * Defines the set of operators which perform vector/matrix math.
     * +  +=  -  -=  *  *=  /  /=  %  %=  <<  <<=  >>  >>=  &  &=  |  |=  ^  ^=
     */
    bool isValidForMatrixOrVector() const;

    /*
     * Defines the set of operators allowed by The OpenGL ES Shading Language 1.00, Section 5.1.
     * The set of illegal (reserved) operators are the ones that only make sense with integral
     * types. This is not a coincidence: It's because ES2 doesn't require 'int' to be anything but
     * syntactic sugar for floats with truncation after each operation.
     */
    bool isAllowedInStrictES2Mode() const
    {
        return !this->isOnlyValidForIntegralTypes();
    }

    /* *
     * Determines the operand and result types of a binary expression. Returns true if the
     * expression is legal, false otherwise. If false, the values of the out parameters are
     * undefined.
     */
    bool determineBinaryType(const Context &context, const Type &left, const Type &right, const Type **outLeftType,
        const Type **outRightType, const Type **outResultType) const;

private:
    bool isOperator() const;
    bool isMatrixMultiply(const Type &left, const Type &right) const;

    Kind fKind;
};
} // namespace SkSL

#endif
